/*
 * Copyright (c) 2019 DawnFramework. All Rights Reserved.
 * Licensed under the MIT (LICENSE) license.
 */

package org.dawn.common.apiclient;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.squareup.okhttp.Response;
import org.dawn.common.apiclient.except.ApiException;
import org.dawn.common.apiclient.except.BaseErrors;
import org.dawn.common.apiclient.json.FastJsonImpl;
import org.dawn.common.apiclient.json.Json;
import org.dawn.common.apiclient.model.ApiMultiResponse;
import org.dawn.common.apiclient.model.ApiResponse;
import org.dawn.common.apiclient.model.Pair;
import org.dawn.common.core.except.IError;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static org.dawn.common.apiclient.ClientUtils.isJsonMime;


/**
 * @author Willard Hu on 2019-04-25.
 */
public abstract class BaseApi {
    protected RestApiClient restApiClient;

    public void setRestApiClient(RestApiClient restApiClient) {
        this.restApiClient = restApiClient;
    }

    // Json序列化工具，默认使用 FastJSON
    protected Json json = new FastJsonImpl();

    public void setJson(Json json) {
        this.json = json;
    }

    /**
     * Convert object to list of Pair
     */
    public List<Pair> handleQueryParams(Object o) {
        JSONObject jobj = (JSONObject) JSON.toJSON(o);
        return jobj.entrySet().stream()
                .filter(item -> !Objects.isNull(item.getValue()))
                .map(item -> new Pair(item.getKey(), ClientUtils.parameterToString(item.getValue(), restApiClient.getDateFormat())))
                .collect(Collectors.toList());
    }

    /**
     * 查询参数处理
     *
     * @param o      Request请求
     * @param fields 需要分装的查询参数
     * @return
     */
    public List<Pair> handleQueryParams(Object o, List<String> fields) {
        JSONObject jobj = (JSONObject) JSON.toJSON(o);
        return jobj.entrySet().stream()
                .filter(fields::contains)
                .filter(it -> !Objects.isNull(jobj.get(it)))
                .map(item -> new Pair(item.getKey(), ClientUtils.parameterToString(item.getValue(), restApiClient.getDateFormat())))
                .collect(Collectors.toList());
    }

    /**
     * Handle the given model, return the deserialized object when the model is successful.
     *
     * @param response Response
     * @param dataType Data type
     * @return Type
     * @throws ApiException If the model has a unsuccessful status code or
     * fail to deserialize the model body
     */
    protected <T> void handleResponse(ApiResponse<T> res, Response response, Class<T> dataType) {
        if (isSuccessful(response)) {
            if (dataType != null) {
                res.setData(deserialize(response, dataType));
                res.setStatusCode(response.code());
            } else {
                if (response.body() != null) {
                    try {
                        response.body().close();
                    } catch (IOException e) {
                        throw new ApiException(e);
                    }
                }
            }
        } else {
            String respBody = null;
            if (response.body() != null) {
                try {
                    respBody = response.body().string();
                } catch (IOException e) {
                    throw new ApiException(e);
                }
            }
            res.setError(takeError(response));
            res.setStatusCode(response.code());
            res.setHeaders(response.headers().toMultimap());
            res.setMessage(respBody);
        }
    }

    /**
     * Handle the given model, return the deserialized object when the model is successful.
     *
     * @param response Response
     * @param dataType Data type
     * @return Type
     * @throws ApiException If the model has a unsuccessful status code or
     * fail to deserialize the model body
     */
    protected <T> void handleMultiResponse(ApiMultiResponse<T> res, Response response, Class<T> dataType) {
        if (isSuccessful(response)) {
            if (dataType != null) {
                res.setList(deserializeList(response, dataType));
            } else {
                if (response.body() != null) {
                    try {
                        response.body().close();
                    } catch (IOException e) {
                        throw new ApiException(e);
                    }
                }
            }
        } else {
            String respBody = null;
            if (response.body() != null) {
                try {
                    respBody = response.body().string();
                } catch (IOException e) {
                    throw new ApiException(e);
                }
            }
            res.setError(takeError(response));
            res.setStatusCode(response.code());
            res.setHeaders(response.headers().toMultimap());
            res.setMessage(respBody);
        }
    }

    /**
     * @return 通过 http status code 获取到对应的异常定义
     */
    protected IError<Integer> takeError(Response response) {
        return BaseErrors.find(response.code());
    }

    /**
     * 判断响应代码是否为请求处理成功
     *
     * @param response Response
     * @return true / false
     */
    protected boolean isSuccessful(Response response) {
        return response.isSuccessful();
    }

    /**
     * Deserialize model body to Java object, according to the return type and
     * the Content-Type model header.
     *
     * @param <T>        Type
     * @param response   HTTP model
     * @param returnType The type of the Java object
     * @return The deserialized Java object
     * @throws ApiException If fail to deserialize model body, i.e. cannot read model body
     * or the Content-Type of the model is not supported.
     */
    @SuppressWarnings("unchecked")
    public <T> T deserialize(Response response, Type returnType) throws ApiException {
        if (response == null || returnType == null) {
            return null;
        }

        if ("byte[]".equals(returnType.toString())) {
            // Handle binary model (byte array).
            try {
                return (T) response.body().bytes();
            } catch (IOException e) {
                throw new ApiException(e);
            }
        }

        String respBody;
        try {
            if (response.body() != null) {
                respBody = response.body().string();
            } else {
                respBody = null;
            }
        } catch (IOException e) {
            throw new ApiException(e);
        }

        if (respBody == null || "".equals(respBody)) {
            return null;
        }

        String contentType = response.headers().get("Content-Type");
        if (contentType == null) {
            // ensuring a default content type
            contentType = "application/json";
        }
        if (isJsonMime(contentType)) {
            return json.deserialize(respBody, returnType);
        } else if (returnType.equals(String.class)) {
            // Expecting string, return the raw model body.
            return (T) respBody;
        } else {
            ApiException except = new ApiException("Content type \"" + contentType + "\" is not supported for type: " + returnType);
            except.setStatusCode(response.code());
            except.setResponseHeaders(response.headers().toMultimap());
            except.setResponseBody(respBody);
            throw except;
        }
    }

    public <T> List<T> deserializeList(Response response, Class<T> dataType) {
        if (response == null || dataType == null) {
            return null;
        }
        String respBody;
        try {
            if (response.body() != null) {
                respBody = response.body().string();
            } else {
                respBody = null;
            }
        } catch (IOException e) {
            throw new ApiException(e);
        }
        if (respBody == null || "".equals(respBody)) {
            return null;
        }
        String contentType = response.headers().get("Content-Type");
        if (contentType == null) {
            // ensuring a default content type
            contentType = "application/json";
        }
        if (isJsonMime(contentType)) {
            return json.deserializeArray(respBody, dataType);
        } else {
            ApiException except = new ApiException("Content type \"" + contentType + "\" is not supported for type: " + dataType);
            except.setStatusCode(response.code());
            except.setResponseHeaders(response.headers().toMultimap());
            except.setResponseBody(respBody);
            throw except;
        }
    }
}
